<!--
 * @Author: liaoyu1992 1519778181@qq.com
 * @Date: 2022-11-05 23:22:15
 * @LastEditors: liaoyu1992 1519778181@qq.com
 * @LastEditTime: 2022-11-11 21:59:52
 * @FilePath: \kgantt\core\components\gantt\Gantt.vue
 * @Description: 
-->
<template>
  <div>
    <ViewSwitcher @on-view-mode-change="onViewModeChange" />
    <div
      class="kgantt-wrapper"
      :on-keydown="handleKeyDown"
      :tabindex="0"
      ref="wrapperRef"
      :style="`height:${ganttHeight ? `${ganttHeight + headerHeight}px` : ''}`"
    >
      <TaskList
        v-if="listCellWidth"
        :header-height="headerHeight"
        :row-width="listCellWidth"
        :font-family="fontFamily"
        :font-size="fontSize"
        :tasks="barTasks"
        :locale="locale"
        :row-height="rowHeight"
        :list-width="listWidth"
        :scroll-y="scrollY"
        :scroll-x="scrollTask"
        :gantt-height="ganttHeight"
        :horizontal-container-class="'kgantt-horizontalContainer'"
        :selected-task="selectedTask"
        :task-list-header="taskListHeader"
        :task-list-table="taskListTable"
        :resource-area="resourceArea"
        :resources="resources"
        @update-list-width="updateTaskListWidth"
        @on-expander-click="handleExpanderClick"
        @set-selected-task="handleSelectedTask"
        @on-task-change="onTaskChange"
      ></TaskList>
      <TaskGantt
        :tasks="tasks"
        :bar-tasks="barTasks"
        :dates="dateSetup.dates"
        :svg-width="svgWidth"
        :row-height="rowHeight"
        :column-width="columnWidth"
        :today-color="todayColor"
        :rtl="rtl"
        :date-setup="dateSetup"
        :locale="locale"
        :view-mode="viewModeRef"
        :header-height="headerHeight"
        :font-family="fontFamily"
        :font-size="fontSize"
        :selected-task="selectedTask"
        :time-step="timeStep"
        :task-height="taskHeight"
        :arrow-color="arrowColor"
        :arrow-indent="arrowIndent"
        :gantt-height="ganttHeight"
        :scroll-x="scrollX"
        :scroll-y="scrollY"
        @set-gantt-event="setGantEvent"
        @on-date-change="onDateChange"
        @on-progress-change="onProgressChange"
        @handle-date-change="handleDateChange"
        @on-click="onClick"
        @on-double-click="onDoubleClick"
        @on-delete="onDelete"
        @set-failed-task="setFailedTask"
      ></TaskGantt>
      <Tooltip
        v-if="ganttEvent.changedTask"
        :arrow-indent="arrowIndent"
        :row-height="rowHeight"
        :svg-container-height="svgContainerHeight"
        :svg-container-width="svgContainerWidth"
        :font-family="fontFamily"
        :font-size="fontSize"
        :scroll-x="scrollX"
        :scroll-y="scrollY"
        :task="ganttEvent.changedTask"
        :header-height="headerHeight"
        :task-list-width="taskListWidth"
        :tooltip-content="tooltipContent"
        :rtl="rtl"
        :svg-width="svgWidth"
      ></Tooltip>
      <VerticalScroll
        :gantt-full-height="ganttFullHeight"
        :gantt-height="ganttHeight"
        :header-geight="headerHeight"
        :scroll="scrollY"
        :rtl="rtl"
        @on-scroll="handleScrollY"
      />
    </div>
    <div>
      <HorizontalScroll
        ref="horizontalScrollRef"
        :svg-width="svgWidth"
        :task-list-view-width="taskListViewWidth"
        :scroll="scrollX"
        :list-width="listWidth"
        :scroll-task="scrollTask"
        :rtl="rtl"
        @on-scroll="handleScrollX"
        @on-scroll-task="handleScrollTask"
      />
    </div>
  </div>
</template>
<script lang="ts" setup>
import TaskList from "../tasklist/TaskList.vue";
import TaskGantt from "./TaskGantt.vue";
import Tooltip from "../other/Tooltip.vue";
import VerticalScroll from "../other/VerticalScroll.vue";
import HorizontalScroll from "../other/HorizontalScroll.vue";
import ViewSwitcher from "../other/ViewSwitcher.vue";
import { computed, onMounted, onUnmounted, PropType, ref, watch } from "vue";
import {
  Resource,
  ResourceArea,
  Task,
  ViewMode,
} from "../../types/publicTypes";
import {
  ganttDateRange,
  getStartEndDateForProject,
  seedDates,
} from "../../helpers/dateHelper";
import { removeHiddenTasks, sortTasks } from "../../helpers/otherHelper";
import { convertToBarTasks } from "../../helpers/barHelper";
import { GanttEvent } from "../../types/ganttTaskActions";
import { DateSetup } from "../../types/dateSetup";
import { BarTask } from "../../types/barTask";

const props = defineProps({
  tasks: {
    type: Array as PropType<Task[]>,
    default: () => [],
  },
  headerHeight: {
    type: Number,
    default: 50,
  },
  listCellWidth: {
    type: Number,
    default: 155,
  },
  rowHeight: {
    type: Number,
    default: 50,
  },
  ganttHeight: {
    type: Number,
    default: 0,
  },
  listWidth: {
    type: String,
    default: "",
  },
  viewMode: {
    type: String as PropType<ViewMode>,
    default: ViewMode.Day,
  },
  preStepsCount: {
    type: Number,
    default: 1,
  },
  locale: {
    type: String,
    default: "zh",
  },
  barFill: {
    type: Number,
    default: 60,
  },
  barCornerRadius: {
    type: Number,
    default: 3,
  },
  barProgressColor: {
    type: String,
    default: "#a3a3ff",
  },
  barProgressSelectedColor: {
    type: String,
    default: "#8282f5",
  },
  barBackgroundColor: {
    type: String,
    default: "#b8c2cc",
  },
  barBackgroundSelectedColor: {
    type: String,
    default: "#aeb8c2",
  },
  projectProgressColor: {
    type: String,
    default: "#7db59a",
  },
  projectProgressSelectedColor: {
    type: String,
    default: "#59a985",
  },
  projectBackgroundColor: {
    type: String,
    default: "#fac465",
  },
  projectBackgroundSelectedColor: {
    type: String,
    default: "#f7bb53",
  },
  milestoneBackgroundColor: {
    type: String,
    default: "#f1c453",
  },
  milestoneBackgroundSelectedColor: {
    type: String,
    default: "#f29e4c",
  },
  rtl: {
    type: Boolean,
    default: false,
  },
  handleWidth: {
    type: Number,
    default: 8,
  },
  timeStep: {
    type: Number,
    default: 300000,
  },
  arrowColor: {
    type: String,
    default: "grey",
  },
  fontFamily: {
    type: String,
    default:
      "Arial, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue",
  },
  fontSize: {
    type: String,
    default: "14px",
  },
  arrowIndent: {
    type: Number,
    default: 20,
  },
  todayColor: {
    type: String,
    default: "rgba(252, 248, 227, 0.5)",
  },
  viewDate: {
    type: Date,
    default: null,
  },
  tooltipContent: {
    type: String,
    default: "",
  },
  taskListHeader: {
    type: String,
    default: "",
  },
  taskListTable: {
    type: String,
    default: "",
  },
  resourceArea: {
    type: Array as PropType<ResourceArea[]>,
    default: () => [],
  },
  resources: {
    type: Array as PropType<Resource[]>,
    default: () => [],
  },
});
const emits = defineEmits([
  "onDateChange",
  "onProgressChange",
  "onDoubleClick",
  "onClick",
  "onDelete",
  "onSelect",
  "onExpanderClick",
]);
let columnWidth = ref(65);
const viewModeRef = ref<ViewMode>(props.viewMode);
const horizontalScrollRef = ref();
const tasksRef = ref<Task[]>(props.tasks);
const wrapperRef = ref<HTMLDivElement>();
const taskListRef = ref<HTMLDivElement>();
const barTasks = ref<BarTask[]>([]);
const [startDate, endDate] = ganttDateRange(
  tasksRef.value,
  viewModeRef.value,
  props.preStepsCount
);
const dateSetup = ref<DateSetup>({
  viewMode: viewModeRef.value,
  dates: seedDates(startDate, endDate, viewModeRef.value),
});
const currentViewDate = ref<Date | undefined>(undefined);
const taskListWidth = ref(0);
const svgContainerWidth = ref(0);
const svgContainerHeight = ref(props.ganttHeight);
const ganttEvent = ref<GanttEvent>({ action: "" });
const taskHeight = computed(() => (props.rowHeight * props.barFill) / 100);
const selectedTask = ref<BarTask>();
const svgWidth = ref(dateSetup.value.dates.length * columnWidth.value);
const taskListViewWidth = ref(props.resourceArea.length * props.listCellWidth);
const ganttFullHeight = computed(() => barTasks.value.length * props.rowHeight);
const scrollY = ref(0);
const scrollX = ref(0);
const scrollTask = ref(0);
const ignoreScrollEvent = ref(false);
const updateColumnWidth = (viewMode: ViewMode) => {
  if (viewMode === ViewMode.Year) {
    columnWidth.value = 350;
  } else if (viewMode === ViewMode.Month) {
    columnWidth.value = 300;
  } else if (viewMode === ViewMode.Week) {
    columnWidth.value = 250;
  } else {
    columnWidth.value = 65;
  }
};
const updateBarTasks = () => {
  let filteredTasks: Task[] = removeHiddenTasks(tasksRef.value as Task[]);
  filteredTasks = filteredTasks.sort(sortTasks);
  const [startDate, endDate] = ganttDateRange(
    filteredTasks,
    viewModeRef.value,
    props.preStepsCount
  );
  let newDates = seedDates(startDate, endDate, viewModeRef.value);
  if (props.rtl) {
    newDates = newDates.reverse();
    if (scrollX.value === -1) {
      scrollX.value = newDates.length * columnWidth.value;
    }
  }
  dateSetup.value = { dates: newDates, viewMode: viewModeRef.value };
  barTasks.value.length = 0;
  barTasks.value = convertToBarTasks(
    filteredTasks,
    newDates,
    columnWidth.value,
    props.rowHeight,
    taskHeight.value,
    props.barCornerRadius,
    props.handleWidth,
    props.rtl,
    props.barProgressColor,
    props.barProgressSelectedColor,
    props.barBackgroundColor,
    props.barBackgroundSelectedColor,
    props.projectProgressColor,
    props.projectProgressSelectedColor,
    props.projectBackgroundColor,
    props.projectBackgroundSelectedColor,
    props.milestoneBackgroundColor,
    props.milestoneBackgroundSelectedColor
  );
};
const updateWithDateSetup = () => {
  if (
    viewModeRef.value === dateSetup.value.viewMode &&
    ((props.viewDate && !currentViewDate.value) ||
      (props.viewDate &&
        currentViewDate.value?.valueOf() !== props.viewDate.valueOf()))
  ) {
    const dates = dateSetup.value.dates;
    const index = dates.findIndex(
      (d, i) =>
        props.viewDate.valueOf() >= d.valueOf() &&
        i + 1 !== dates.length &&
        props.viewDate.valueOf() < dates[i + 1].valueOf()
    );
    if (index === -1) {
      return;
    }
    currentViewDate.value = props.viewDate;
    scrollX.value = columnWidth.value * index;
  }
};
const updateSvgContanierWidth = () => {
  if (wrapperRef.value) {
    svgContainerWidth.value =
      wrapperRef.value.offsetWidth - taskListWidth.value;
  }
};
const onViewModeChange = (viewMode: ViewMode) => {
  updateColumnWidth(viewMode);
  viewModeRef.value = viewMode;
  updateWithDateSetup();
  updateBarTasks();
};
const updateSvgContainerHeight = () => {
  if (props.ganttHeight) {
    svgContainerHeight.value = props.ganttHeight + props.headerHeight;
  } else {
    svgContainerHeight.value =
      tasksRef.value.length * props.rowHeight + props.headerHeight;
  }
};
const setGantEvent = (event: GanttEvent) => {
  ganttEvent.value = event;
};
const onDateChange = (
  task: Task,
  children: Task[],
  cackball: (result: boolean | undefined) => void
) => {
  emits("onDateChange", task, children);
  if (cackball) {
    cackball(undefined);
  }
};
const onProgressChange = (task: Task, children: Task[]) => {
  emits("onProgressChange", task, children);
};
const updateTaskListWidth = (width: number) => {
  if (!props.listCellWidth) {
    taskListWidth.value = 0;
  }
  if (taskListRef.value) {
    taskListWidth.value = width;
  }
  updateColumnWidth(viewModeRef.value);
  horizontalScrollRef.value.show(width);
  updateSvgContainerHeight();
  updateSvgContanierWidth();
  updateBarTasks();
  updateWithDateSetup();
};
const onClick = (task: BarTask) => {
  const newSelectedTask = barTasks.value.find((t) => t.id === task.id);
  const oldSelectedTask = barTasks.value.find(
    (t) => !!selectedTask.value && t.id === selectedTask.value?.id
  );
  if (oldSelectedTask) {
    emits("onSelect", oldSelectedTask, false);
  }
  if (newSelectedTask) {
    emits("onSelect", newSelectedTask, true);
  }
  selectedTask.value = newSelectedTask;
  emits("onClick", task);
};
const setFailedTask = (task: BarTask) => {
  barTasks.value = barTasks.value?.map((t: any) =>
    t.id !== task?.id ? t : task
  );
};
const onDelete = (task: BarTask) => {
  emits("onDelete", task);
};
const onDoubleClick = (task: BarTask) => {
  emits("onDoubleClick", task);
};
const handleWheel = (event: WheelEvent) => {
  if (event.shiftKey || event.deltaX) {
    const scrollMove = event.deltaX ? event.deltaX : event.deltaY;
    let newScrollX = scrollX.value + scrollMove;
    if (newScrollX < 0) {
      newScrollX = 0;
    } else if (newScrollX > svgWidth.value) {
      newScrollX = svgWidth.value;
    }
    scrollY.value = newScrollX;
    event.preventDefault();
  } else if (props.ganttHeight) {
    let newScrollY = scrollY.value + event.deltaY;
    if (newScrollY < 0) {
      newScrollY = 0;
    } else if (newScrollY > ganttFullHeight.value - props.ganttHeight) {
      newScrollY = ganttFullHeight.value - props.ganttHeight;
    }
    if (newScrollY !== scrollY.value) {
      scrollY.value = newScrollY;
      event.preventDefault();
    }
  }
  ignoreScrollEvent.value = true;
};
const handleScrollY = (event: Event) => {
  if (
    scrollY.value !== (event.currentTarget as HTMLDivElement).scrollTop &&
    !ignoreScrollEvent.value
  ) {
    scrollY.value = (event.currentTarget as HTMLDivElement).scrollTop;
    ignoreScrollEvent.value = true;
  } else {
    ignoreScrollEvent.value = false;
  }
};

const handleScrollX = (event: Event) => {
  if (
    scrollX.value !== (event.currentTarget as HTMLDivElement).scrollLeft &&
    !ignoreScrollEvent.value
  ) {
    scrollX.value = (event.currentTarget as HTMLDivElement).scrollLeft;
    ignoreScrollEvent.value = true;
  } else {
    ignoreScrollEvent.value = false;
  }
};
const handleScrollTask = (event: Event) => {
  if (
    scrollTask.value !== (event.currentTarget as HTMLDivElement).scrollLeft &&
    !ignoreScrollEvent.value
  ) {
    scrollTask.value = (event.currentTarget as HTMLDivElement).scrollLeft;
    ignoreScrollEvent.value = true;
  } else {
    ignoreScrollEvent.value = false;
  }
};
const handleDateChange = (task: Task) => {
  let tasksTmp: Task[] = [...tasksRef.value];
  tasksTmp = tasksTmp.map((t) => (task.id === t.id ? task : t));
  if (task.project) {
    const [start, end] = getStartEndDateForProject(tasksTmp, task.project);
    const project = tasksTmp[tasksTmp.findIndex((t) => t.id === task.project)];
    if (
      project.start.getTime() !== start.getTime() ||
      project.end.getTime() !== end.getTime()
    ) {
      const changedProject = { ...project, start, end };
      tasksTmp = tasksTmp.map((t) =>
        t.id === task.project ? changedProject : t
      );
    }
  }
  tasksRef.value = tasksTmp;
  updateBarTasks();
};
const handleExpanderClick = (task: Task) => {
  const tasksTmp: Task[] = [...tasksRef.value];
  tasksRef.value = tasksTmp.map((t) =>
    task.id === t.id ? { ...task, hideChildren: !task.hideChildren } : t
  );
  updateBarTasks();
  if (task.hideChildren !== undefined) {
    emits("onExpanderClick", { ...task, hideChildren: !task.hideChildren });
  }
};
const handleSelectedTask = (taskId: string) => {
  const newSelectedTask = barTasks.value.find((t) => t.id === taskId);
  const oldSelectedTask = barTasks.value.find(
    (t) => !!selectedTask.value && t.id === selectedTask.value?.id
  );
  if (oldSelectedTask) {
    emits("onSelect", oldSelectedTask, false);
  }
  if (newSelectedTask) {
    emits("onSelect", newSelectedTask, true);
  }
  selectedTask.value = newSelectedTask;
};
const onTaskChange = (task: Task) => {
  if(typeof task.start === "string"){
    task.start = new Date(task.start);
  }
  if(typeof task.end === "string"){
    task.end = new Date(task.end);
  }
  let tasksTmp: Task[] = [...tasksRef.value];
  tasksTmp = tasksTmp.map((t) => (task.id === t.id ? task : t));
  if (task.project) {
    const [start, end] = getStartEndDateForProject(tasksTmp, task.project);
    const project = tasksTmp[tasksTmp.findIndex((t) => t.id === task.project)];
    if (
      project.start.getTime() !== start.getTime() ||
      project.end.getTime() !== end.getTime()
    ) {
      const changedProject = { ...project, start, end };
      tasksTmp = tasksTmp.map((t) =>
        t.id === task.project ? changedProject : t
      );
    }
  }
  tasksRef.value = tasksTmp;
  updateBarTasks();
};
const handleKeyDown = (event: KeyboardEvent) => {
  event.preventDefault();
  let newScrollY = scrollY.value;
  let newScrollX = scrollX.value;
  let isX = true;
  switch (event.key) {
    case "Down": // IE/Edge specific value
    case "ArrowDown":
      newScrollY += props.rowHeight;
      isX = false;
      break;
    case "Up": // IE/Edge specific value
    case "ArrowUp":
      newScrollY -= props.rowHeight;
      isX = false;
      break;
    case "Left":
    case "ArrowLeft":
      newScrollX -= columnWidth.value;
      break;
    case "Right": // IE/Edge specific value
    case "ArrowRight":
      newScrollX += columnWidth.value;
      break;
  }
  if (isX) {
    if (newScrollX < 0) {
      newScrollX = 0;
    } else if (newScrollX > svgWidth.value) {
      newScrollX = svgWidth.value;
    }
    scrollX.value = newScrollX;
  } else {
    if (newScrollY < 0) {
      newScrollY = 0;
    } else if (newScrollY > ganttFullHeight.value - props.ganttHeight) {
      newScrollY = ganttFullHeight.value - props.ganttHeight;
    }
    scrollY.value = newScrollY;
  }
  ignoreScrollEvent.value = true;
};
watch(
  () => props.tasks,
  () => {
    tasksRef.value.length = 0;
    tasksRef.value = props.tasks;
    updateBarTasks();
    updateSvgContainerHeight();
  }
);
watch(
  () => [dateSetup.value],
  () => {
    svgWidth.value = dateSetup.value.dates.length * columnWidth.value;
    updateWithDateSetup();
  }
);
watch(
  () => ganttEvent.value,
  () => {
    const { changedTask, action } = ganttEvent.value;
    if (changedTask) {
      if (action === "delete") {
        ganttEvent.value = { action: "" };
        barTasks.value = barTasks.value.filter(
          (t: any) => t.id !== changedTask.id
        );
      } else if (
        action === "move" ||
        action === "end" ||
        action === "start" ||
        action === "progress"
      ) {
        const prevStateTask = barTasks.value.find(
          (t: any) => t.id === changedTask.id
        );
        if (
          prevStateTask &&
          (prevStateTask.start.getTime() !== changedTask.start.getTime() ||
            prevStateTask.end.getTime() !== changedTask.end.getTime() ||
            prevStateTask.progress !== changedTask.progress)
        ) {
          const newTaskList = barTasks.value.map((t: any) =>
            t.id === changedTask.id ? changedTask : t
          );
          barTasks.value = newTaskList;
        }
      }
    }
  }
);
onMounted(() => {
  wrapperRef.value?.addEventListener("wheel", handleWheel, {
    passive: false,
  });
});
onUnmounted(() => {
  wrapperRef.value?.removeEventListener("wheel", handleWheel);
});
</script>